// ManagedVC1Encoder.cpp : main project file.

#include "stdafx.h"
#include <vcclr.h>
#include "ManagedVC1Encoder.h"

using namespace System;

namespace ManagedVC1
{
    using namespace Exceptions;

    void VC1Encoder::Initialize(String^ inputFile)
    {
		m_cancel = false;
        InitFileReader(inputFile);

        System::Drawing::Size^ videoSize = m_FileReader.VideoSize;
        Config->MaxHeight = videoSize->Height;
        Config->MaxWidth = videoSize->Width;

        m_pEncoder->Configure(m_ConfigProperties->GetConfigProperties());
    }

    void VC1Encoder::InitFileReader(String^ inputFile)
    {
        m_FileReader.Initialize(inputFile, Config->FrameRate);
    }

    void VC1Encoder::InitFileWriter(String^ outputFile, String^ profileName)
    {
        long hr = 0;
        AutoArrayPtr<BYTE> privCodecData;
        unsigned long privCodecDataLen = 0;
        
        unsigned long rateControlMode = Config->RateControlMode;
        
        m_FileWriter.VideoSize = m_FileReader.VideoSize;
        m_FileWriter.FrameRate = Config->FrameRate;
        m_FileWriter.FrameSizeInBytes = m_vc1BufferSize;
        m_FileWriter.VC1Profile = Config->Profile;
        m_FileWriter.IsVBR = (rateControlMode == 1 || rateControlMode == 3 || rateControlMode == 4);
        m_FileWriter.Bitrate = Config->BitRate * 1000;

        hr = m_pEncoder->GetCodecPrivateData(nullptr, &privCodecDataLen, nullptr);
        ExceptionUtility::ThrowExceptionForHr(hr);

        privCodecData.Reset(new BYTE[privCodecDataLen]);
        ZeroMemory(privCodecData, privCodecDataLen);
        hr = m_pEncoder->GetCodecPrivateData(privCodecData, &privCodecDataLen, nullptr);
        ExceptionUtility::ThrowExceptionForHr(hr);

        if (!m_FileWriter.IsVBR && Config->BitRate > 0)
        {
            m_FileWriter.BufferInMilliseconds = (int)(Config->VBVBufferInBytes * 8 / Config->BitRate);
        }
        else
        {
            m_FileWriter.BufferInMilliseconds = -1;
        }

        m_FileWriter.Initialize(outputFile, profileName, privCodecData, privCodecDataLen, m_FileReader.AudioInfoHeader);
    }

    void VC1Encoder::Encode(String^ outputFile, String^ profileName)
    {
        AutoArrayPtr<BYTE> videoData;
        FrameType frameType;
        
        long hr = 0;
        unsigned long totalFrames = 0;
        unsigned long vc1size = 0;
        __int64 timeStampOut = 0;

        pin_ptr<unsigned long> bufSize = &m_vc1BufferSize;
        m_pEncoder->GetMaxOutputBuffer(bufSize);
        m_vc1Data.Reset(new BYTE[m_vc1BufferSize]);

        Analyze();

        totalFrames = m_FileReader.FrameCount;
        m_FileReader.SeekToBeginning();

        hr = m_pEncoder->StartEncode();
        ExceptionUtility::ThrowExceptionForHr(hr);

        try
        {
            InitFileWriter(outputFile, profileName);

            try
            {
                for (unsigned long frame = 0; frame < totalFrames; frame++)
                {
                    System::Threading::Monitor::Enter(lockObj);
                    if (m_cancel)
                    {
                        System::Threading::Monitor::Exit(lockObj);
                        break;
                    }
                    System::Threading::Monitor::Exit(lockObj);

                    m_aviReadTime.Start();
                    __int64 timeStampIn = m_FileReader.ReadVideoFrame(videoData);
                    m_aviReadTime.Stop();

                    ZeroMemory(m_vc1Data, m_vc1BufferSize);
                    m_vc1EncodeTime.Start();
                    hr = m_pEncoder->EncodeFrame(videoData, m_vc1Data, &vc1size, timeStampIn, &timeStampOut, 0, 0, &frameType);
                    m_vc1EncodeTime.Stop();

                    if ((frame % 10 == 0 || frame == (totalFrames - 1)))
		            {
                        ProgressChanged(this, gcnew System::ComponentModel::ProgressChangedEventArgs((int)(((frame + 1) * 1.0 / totalFrames) * 100.0), nullptr));
		            }

                    if (hr == ERR_OK)
                    {
                        WriteAudioVideo(m_vc1Data, vc1size, timeStampOut, frameType);
                    }
                    else if (hr == ERR_NO_OP_FRAME) continue;
                    else
                    {
                        ExceptionUtility::ThrowExceptionForHr(hr);
                    }
                }

                System::Threading::Monitor::Enter(lockObj);
                if (!m_cancel)
                {
                    FlushVideo();
                    FlushAudio();
                }
                System::Threading::Monitor::Exit(lockObj);
            }
            finally
            {
                m_FileWriter.Stop();
            }
        }
        finally
        {
            DEBUG("AVI Read Time: " + m_aviReadTime.Elapsed.ToString());
            DEBUG("VC1 Encode Time: " + m_vc1EncodeTime.Elapsed.ToString());

            hr = m_pEncoder->EndEncode();
            ExceptionUtility::ThrowExceptionForHr(hr);
        }
    }

    void VC1Encoder::Analyze()
    {
        if (Config->RateControlMode < 2) return;

        AutoArrayPtr<BYTE> videoData;
        long hr = 0;
        unsigned long frameCount = m_FileReader.FrameCount;
        m_FileReader.SeekToBeginning();

        hr = m_pEncoder->StartAnalyze();
        ExceptionUtility::ThrowExceptionForHr(hr, "CVC1Encoder::StartAnalyze");

        try
        {
            for (unsigned long frame = 0; frame < frameCount ; frame++)
            {
                m_FileReader.ReadVideoFrame(videoData);
                hr = m_pEncoder->AnalyzeFrame(videoData);
                ExceptionUtility::ThrowExceptionForHr(hr, "CVC1Encoder::AnalyzeFrame");
            }
        }
        finally
        {
            hr = m_pEncoder->EndAnalyze();
            ExceptionUtility::ThrowExceptionForHr(hr, "CVC1Encoder::EndAnalyze");
        }
    }

    void VC1Encoder::FlushVideo()
    {
        long hr = 0;
        __int64 timeStampOut = 0;
        unsigned long vc1size = 0;
        FrameType frameType;

        while (hr != ERR_NOMORE_FRAMES)
        {
            ZeroMemory(m_vc1Data, m_vc1BufferSize);
            hr = m_pEncoder->Flush(m_vc1Data, &vc1size, &timeStampOut, &frameType);
            if ((hr == ERR_OK || hr == ERR_NOMORE_FRAMES) && vc1size > 0)
            {
                WriteAudioVideo(m_vc1Data, vc1size, timeStampOut, frameType);
            }
        }

        if (hr != ERR_NOMORE_FRAMES) ExceptionUtility::ThrowExceptionForHr(hr);
    }

    void VC1Encoder::FlushAudio()
    {
        long hr = 0;
        __int64 timeStampAudio = 0;
        BYTE* audioData;
        long audioDataSize = 0;

        do
        {
            timeStampAudio = m_FileReader.ReadAudioFrame(&audioData, &audioDataSize);

            if (audioDataSize > 0)
            {
                m_FileWriter.WriteAudioFrame(audioData, audioDataSize, timeStampAudio);
            }
        }
        while (audioDataSize > 0);
    }

    void VC1Encoder::WriteAudioVideo(BYTE* pVC1Data, unsigned long vc1DataSize, __int64 timeStampVideo, FrameType ft)
    {
        long hr = 0;
        __int64 timeStampAudio = 0;
        BYTE* audioData;
        long audioDataSize = 0;

        timeStampAudio = m_FileReader.ReadAudioFrame(&audioData, &audioDataSize);

        if (audioData == nullptr && audioDataSize == 0) { }
        else
        {
            m_FileWriter.WriteAudioFrame(audioData, audioDataSize, timeStampAudio);
        }

        if (vc1DataSize > 0)
        {
            m_FileWriter.WriteVideoFrame(pVC1Data, vc1DataSize, timeStampVideo, ft);
        }
    }

    void VC1Encoder::Cancel()
    {
        System::Threading::Monitor::Enter(lockObj);
        m_cancel = true;
        System::Threading::Monitor::Exit(lockObj);
    }
}
